#ifndef _MCACHE2_H
#define _MCACHE2_H

#include <libmemcached/memcached.h>

#include <vector>
#include <map>
#include <string>
#include <iostream>

#ifdef USE_POCO_LOGGER
#include <Poco/Logger.h>
#include <Poco/Format.h>
#endif

namespace mcache {

	using namespace std;
#ifdef USE_POCO_LOGGER
	using namespace Poco;
#endif

	class Cache2
	{
	public:
		Cache2() 
			: m_cache(memcached_create(0L)) 
#ifdef USE_POCO_LOGGER
			, m_logger(&Logger::get("memcache"))
#endif
		{}
		~Cache2() { memcached_free(m_cache); }

	public:

		/**
		 * Add a server to the cache.
		 * @param addr server's address (host:port)
		 * @return 0 success
		 */
		int addServer(const string& addr) { 
			string host;
			int port;
			memcached_return rc;
			if (addr.find("udp:") == 0) {
				parse_hostip(addr.substr(4), host, port);
				rc = memcached_server_add_udp(m_cache, const_cast<char*>(host.c_str()), port);	
			} else if (addr.find("unix:") == 0) {
				rc = memcached_server_add_unix_socket(m_cache, const_cast<char*>(addr.substr(5).c_str()));
			} else if (addr.find("tcp:") == 0) {
				parse_hostip(addr.substr(4), host, port);
				rc = memcached_server_add(m_cache, const_cast<char*>(host.c_str()), port);
			} else {
				parse_hostip(addr, host, port);
				rc = memcached_server_add(m_cache, const_cast<char*>(host.c_str()), port);
			}
#ifdef USE_POCO_LOGGER
			poco_debug(*m_logger, format("addServer('%s') = %s", addr, strerror(rc)));
#endif
			return rc;
		}

		string get(const string& key, memcached_return* rc2 = 0L)
		{
			string keyen = encodeKey(key);
			size_t len = 0;
			uint16_t flags = 0;
			memcached_return rc = MEMCACHED_SUCCESS;
			char* ret = memcached_get(m_cache,
				const_cast<char*>(keyen.data()), keyen.size(), 
				&len, &flags, &rc);
#ifdef USE_POCO_LOGGER
			poco_debug(*m_logger, format("get('%s') return %s", keyen, strerror(rc)));
#endif

			if (rc2 != 0L)
				*rc2 = rc;

			string s;
			if (rc == MEMCACHED_SUCCESS && len > 0)
			{
				s.assign(ret, len);
				free(ret);
			}
			return s;
		}

		string get2(const string& key) {
			return get(key);
		}

		bool add(const string& key, const string& value, const time_t expires = 0, const u_int16_t flags=0)
		{
			string keyen = encodeKey(key);
			memcached_return rc = memcached_add(m_cache, 
					const_cast<char*>(keyen.data()), keyen.size(), 
					const_cast<char*>(value.data()), value.size(), expires, flags);
#ifdef USE_POCO_LOGGER
			poco_debug(*m_logger, format("add('%s') return %s", keyen, strerror(rc)));
#endif
			return rc == MEMCACHED_SUCCESS;
		}
		
		int add2(const string& key, const string& value, const time_t expires = 0, const u_int16_t flags=0)
		{
			string keyen = encodeKey(key);
			memcached_return rc = memcached_add(m_cache, 
					const_cast<char*>(keyen.data()), keyen.size(), 
					const_cast<char*>(value.data()), value.size(), expires, flags);
#ifdef USE_POCO_LOGGER
			poco_debug(*m_logger, format("add2('%s') return %s", keyen, strerror(rc)));
#endif
			return rc;
		}

		bool set(const string& key, const string& value, const time_t expires = 0, const u_int16_t flags=0)
		{
			string keyen = encodeKey(key);
			memcached_return rc = memcached_set(m_cache, 
					const_cast<char*>(keyen.data()), keyen.size(), 
					const_cast<char*>(value.data()), value.size(), expires, flags);
#ifdef USE_POCO_LOGGER
			poco_debug(*m_logger, format("set('%s') return %s", keyen, strerror(rc)));
#endif
			return rc == MEMCACHED_SUCCESS;
		}

		bool remove(const string& key, const time_t hold = 0)
		{
			string keyen = encodeKey(key);
			memcached_return rc = memcached_delete(m_cache, 
							const_cast<char*>(keyen.data()), keyen.size(),
							hold);
#ifdef USE_POCO_LOGGER
			poco_debug(*m_logger, format("remove('%s') return %s", keyen, strerror(rc)));
#endif
			return rc == MEMCACHED_SUCCESS;
		}

		/**
		* get several keys' values. 
		* @param keys the keys
		* @param results store the pairs found in the cache.
		*/
		/*
		void multi_get(const vector<string>& keys, map<string, string>& results)
		{
			if (keys.empty())
				return;
			
			char* szkeys[4096]; //TODO max 4096 keys
			size_t keylens[4096];
			vector<string> encodedKeys;
			for(int i=0; i<keys.size(); i++)
			{
				encodedKeys.push_back(encodeKey(key));
				szkeys[i] = encodedKeys[i].c_str();
				keylens[i] = encodedKeys[i].length();
			}

			if (0 != memcached_mget(m_cache, szkeys, keylens, keys.size())) 
				return;

			memcached_return rc;
			memcached_result_st* res = memcached_fetch_result(m_cache, 0L, &rc);
			if (rc != 0)
				return;

			memcached_result_free(res);
		}
		*/

	private:
		string strerror(memcached_return rc)
		{
			return string(memcached_strerror(m_cache, rc));
		}

		string encodeKey(const string& src)
		{
			string ret = src;
			string::size_type pos = 0;
			while ( (pos = ret.find(' ', pos)) != string::npos ) {
				ret.replace(pos, 1, 1, '+');
				pos++;
				if (pos == ret.length()) break;
			}
#ifdef USE_POCO_LOGGER
			poco_trace(*m_logger, format("encodeKey('%s') = '%s'", src, ret));
#endif
			return ret;
		}

		string decodeKey(const string& src)
		{
			string ret = src;
			string::size_type pos = 0;
			while ( (pos = ret.find('+', pos)) != string::npos ) {
				ret.replace(pos, 1, 1, ' ');
				pos++;
				if (pos == ret.length()) break;
			}
			return ret;
		}

		bool parse_hostip(const string& s, string& host, int& port) {
			string::size_type pos = s.find(':');
			if (pos == string::npos) {
				host = s;
				port = 0;
			} else {
				host = s.substr(0, pos);
				port = ::atoi(s.substr(pos+1).c_str());
			}
			return true;
		}
	private:
		memcached_st* m_cache;
#ifdef USE_POCO_LOGGER
		Poco::Logger* m_logger;
#endif
	};

}

#endif

